package org.maplanning;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;

import java.util.ArrayList;
import java.util.Arrays;

import org.junit.Test;
import org.maplanning.GridMap.RunResults;
import org.maplanning.feature.rect.Door;
import org.maplanning.feature.rect.RectRoomFeature;


public class GridMapTest {

	@Test
	public void testLoadMap() {
		GridMap map = GridMap.loadMap("maps/2rooms.txt");
		assertEquals("Map height", 6, map.height);
		assertEquals("Map width", 12, map.width);

		// two goal states:
		assertEquals("# of goal states", 2, map.goalStates.size() );
		assertArrayEquals("goal 0", new int[]{1,2}, map.goalStates.get(0));
		assertArrayEquals("goal 1", new int[]{1,3}, map.goalStates.get(1));

		// initialize agent:
		map.initializeAgent();
		assertEquals("Agent row", 2, map.agent[0]);
		assertEquals("Agent col", 9, map.agent[1]);

		// move toward wall:
		map.move('R');
		assertEquals("Agent row", 2, map.agent[0]);
		assertEquals("Agent col", 10, map.agent[1]);
		map.move('R');
		assertEquals("Agent row", 2, map.agent[0]);
		assertEquals("Agent col", 10, map.agent[1]);

		// moving back:
		map.move('L');
		assertEquals("Agent row", 2, map.agent[0]);
		assertEquals("Agent col", 9, map.agent[1]);

		map.move('U');
		assertEquals("Agent row", 1, map.agent[0]);
		assertEquals("Agent col", 9, map.agent[1]);
		map.move('U');
		assertEquals("Agent row", 1, map.agent[0]);
		assertEquals("Agent col", 9, map.agent[1]);
		map.move('D');
		assertEquals("Agent row", 2, map.agent[0]);
		assertEquals("Agent col", 9, map.agent[1]);

		// move to goal:
		map.move('L', 'L', 'D', 'L', 'L', 'L', 'L', 'U', 'U');
		assertEquals("Agent row", 1, map.agent[0]);
		assertEquals("Agent col", 3, map.agent[1]);
		assertEquals("Goal", GridMap.GOAL, map.get(map.agent) );
	}

	@Test
	public void testExecuteStrategy() {
		GridMap map = GridMap.loadMap("maps/2rooms.txt");
		FixedStrat fs = new FixedStrat('L', 'L', 'D', 'D');

		RunResults rr = map.executeStrategy(fs, 1);
		assertEquals("State path length", 2, rr.path.size());
		assertEquals("Actions", 1, rr.actions.size());
		assertEquals("Goal", false, rr.reachedGoal);

		assertArrayEquals("Agent@0", new int[]{2,9}, rr.path.get(0));
		assertArrayEquals("Agent@1", new int[]{2,8}, rr.path.get(1));

		rr = map.executeStrategy(fs, 5);
		assertEquals("State path length", 6, rr.path.size());
		assertEquals("Actions", 5, rr.actions.size());
		assertEquals("Goal", false, rr.reachedGoal);
		assertArrayEquals("Agent@4", new int[]{4,7}, rr.path.get(4));
		assertArrayEquals("Agent@5", new int[]{4,6}, rr.path.get(5));

		// Reaching goal:
		fs = new FixedStrat('L', 'L', 'L', 'L', 'D', 'L', 'L', 'U', 'L', 'U', 'R');
		rr = map.executeStrategy(fs, 40);
		assertEquals("State path length", 11, rr.path.size());
		assertEquals("Actions", 10, rr.actions.size());
		assertEquals("Goal", true, rr.reachedGoal);
		assertArrayEquals("Agent@10", new int[]{1,3}, rr.path.get(rr.path.size()-1));

		System.out.println(map);
	}

	@Test
	public void testUtilities() {
		GridMap map = GridMap.loadMap("maps/2rooms.txt");
		ArrayList<int[]> neighbors = map.getNeighbors(new int[]{2,3});
		assertEquals("# neighbors", 4, neighbors.size());
		// checking if all 4 neighbors present:
		ArrayList<int[]> expNeighbors = new ArrayList<int[]>();
		expNeighbors.add( new int[]{1,3} );
		expNeighbors.add( new int[]{3,3} );
		expNeighbors.add( new int[]{2,2} );
		expNeighbors.add( new int[]{2,4} );
		assertListContained(expNeighbors, neighbors);

		// corner case:
		neighbors = map.getNeighbors(new int[]{1,4});
		assertEquals("# neighbors", 2, neighbors.size());
		expNeighbors = new ArrayList<int[]>();
		expNeighbors.add( new int[]{1,3} );
		expNeighbors.add( new int[]{2,4} );
		assertListContained(expNeighbors, neighbors);

		// extreme corner case:
		neighbors = map.getNeighbors(new int[]{0,0});
		assertEquals("# neighbors", 0, neighbors.size());

		// other extreme corner case:
		neighbors = map.getNeighbors(new int[]{map.height-1,map.width-1});
		assertEquals("# neighbors", 0, neighbors.size());

		// testing getAction:
		assertEquals("Up action", 'U', map.getAction(map.actionMap.get('U')) );
		assertEquals("Down action", 'D', map.getAction(map.actionMap.get('D')) );
		assertEquals("Left action", 'L', map.getAction(map.actionMap.get('L')) );
		assertEquals("Right action", 'R', map.getAction(map.actionMap.get('R')) );
	}

	/**
	 * Asserts that all {@code list} elements are present in {@code container}.
	 */
	public static void assertListContained(ArrayList<int[]> list, ArrayList<int[]> container) {
		outer:
		for (int[] x1 : list) {
			for (int[] x2: container) {
				if (Arrays.equals(x1, x2)) {
					continue outer;
				}
			}
			// no match found:
			fail("Element "+Arrays.toString(x1)+" not in list container.");
		}
	}

	@Test
	public void testAStar() {
		GridMap map = GridMap.loadMap("maps/2rooms.txt");
		AStarStrat astar = new AStarStrat();
		RunResults rr = map.executeStrategy(astar, 20);

		assertEquals("Length of shortest route", 10, rr.path.size());
		assertArrayEquals("Passes door @ 5", new int[]{3,5}, rr.path.get(5));
		assertEquals("Reaches goal", true, rr.reachedGoal);
	}

	@Test
	public void testGreedy() {
		GridMap map = GridMap.loadMap("maps/2rooms.txt");
		GreedyStrat greedy = new GreedyStrat();
		RunResults rr = map.executeStrategy(greedy, 20);

		assertEquals("Agent did all steps", 21, rr.path.size());
		assertEquals("Reaches goal", false, rr.reachedGoal);


		// successful case, if agent starts between doors:
		rr = map.executeStrategy(greedy, 20, new int[]{3, 5} );
		assertEquals("Agent did all steps", 5, rr.path.size());
		assertEquals("Reaches goal", true, rr.reachedGoal);

	}

	@Test
	public void testRectRoomFeature() {
		GridMap grid = GridMap.loadMap("maps/large-4rooms-clean.txt");
		RectRoomFeature rrf = new RectRoomFeature();
		rrf.detect(grid, new int[]{45, 40});

		assertArrayEquals("Room min", new int[]{28, 29}, rrf.getRoom().getBoundMin());
		assertArrayEquals("Room max", new int[]{60, 59}, rrf.getRoom().getBoundMax());

		assertEquals("Number of doors", 2, rrf.getDoors().size());

		ArrayList<Door> doors = new ArrayList<Door>(2);
		Door door = new Door(new int[]{44, 29}, new int[]{0, -1});
		door.expandTo(new int[]{45, 29});
		doors.add(door);

		door = new Door(new int[]{57, 29}, new int[]{0, -1});
		door.expandTo(new int[]{58, 29});
		doors.add(door);

		assertEquals("Doors", true, rrf.getDoors().containsAll(doors));
	}

	@Test
	public void testRectRoomFeature2() {
		GridMap grid = GridMap.loadMap("maps/large-4rooms-clean.txt");
		RectRoomFeature rrf = new RectRoomFeature();
		rrf.detect(grid, new int[]{55, 12});

		assertArrayEquals("Room min", new int[]{28, 0}, rrf.getRoom().getBoundMin());
		assertArrayEquals("Room max", new int[]{60, 29}, rrf.getRoom().getBoundMax());

		assertEquals("Number of doors", 3, rrf.getDoors().size());

		ArrayList<Door> doors = new ArrayList<Door>(3);
		Door door = new Door(new int[]{28, 12}, new int[]{-1, 0});
		doors.add(door);

		door = new Door(new int[]{44, 29}, new int[]{0, 1});
		door.expandTo(new int[]{45, 29});
		doors.add(door);

		door = new Door(new int[]{57, 29}, new int[]{0, 1});
		door.expandTo(new int[]{58, 29});
		doors.add(door);

		assertEquals("Doors", true, rrf.getDoors().containsAll(doors));
	}

	@Test
	public void testRectRoomFeature3() {
		GridMap grid = GridMap.loadMap("maps/large-4rooms-clean.txt");
		RectRoomFeature rrf = new RectRoomFeature();
		rrf.detect(grid, new int[]{24, 26});

		assertArrayEquals("Room min", new int[]{0, 0}, rrf.getRoom().getBoundMin());
		assertArrayEquals("Room max", new int[]{28, 29}, rrf.getRoom().getBoundMax());

		assertEquals("Number of doors", 2, rrf.getDoors().size());

		ArrayList<Door> doors = new ArrayList<Door>(2);
		Door door = new Door(new int[]{28, 12}, new int[]{1, 0});
		doors.add(door);

		door = new Door(new int[]{12, 29}, new int[]{0, 1});
		door.expandTo(new int[]{13, 29});
		doors.add(door);

		assertEquals("Doors", true, rrf.getDoors().containsAll(doors));
	}

	@Test
	public void testRectRoomFeatureNearWall() {
		GridMap grid = GridMap.loadMap("maps/large-4rooms-clean.txt");
		RectRoomFeature rrf = new RectRoomFeature();
		rrf.detect(grid, new int[]{27, 28});

		assertArrayEquals("Room min", new int[]{0, 0}, rrf.getRoom().getBoundMin());
		assertArrayEquals("Room max", new int[]{28, 29}, rrf.getRoom().getBoundMax());

		assertEquals("Number of doors", 2, rrf.getDoors().size());

		ArrayList<Door> doors = new ArrayList<Door>(2);
		Door door = new Door(new int[]{28, 12}, new int[]{1, 0});
		doors.add(door);

		door = new Door(new int[]{12, 29}, new int[]{0, 1});
		door.expandTo(new int[]{13, 29});
		doors.add(door);

		assertEquals("Doors", true, rrf.getDoors().containsAll(doors));
	}
	
	@Test
	public void testRectRoomOnMapEdge() {
		GridMap grid = GridMap.loadMap("maps/large-4rooms-clean.txt");
		RectRoomFeature rrf = new RectRoomFeature();
		// bottom and right edges:
		rrf.detect(grid, new int[]{59, 58});

		assertArrayEquals("Room min", new int[]{28, 29}, rrf.getRoom().getBoundMin());
		assertArrayEquals("Room max", new int[]{60, 59}, rrf.getRoom().getBoundMax());

		assertEquals("Number of doors", 2, rrf.getDoors().size());
		
		
		// top and left edges:
		rrf = new RectRoomFeature();
		rrf.detect(grid, new int[]{1, 1});

		assertArrayEquals("Room min", new int[]{0, 0}, rrf.getRoom().getBoundMin());
		assertArrayEquals("Room max", new int[]{28, 29}, rrf.getRoom().getBoundMax());

		assertEquals("Number of doors", 2, rrf.getDoors().size());
	}


	@Test
	public void html5Test() {
		GridMap grid = GridMap.loadMap("maps/large-4rooms-clean.txt");
		GreedyStrat greedy = new GreedyStrat();
		RunResults run = grid.executeStrategy(greedy, 20);
		
		grid.outputHtml5("results/4rooms-1.html", run);
	}
}
